﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using DotNetNuke.Common.Utilities;
using iFinity.DNN.Modules.UrlMaster;
using iFinity.DNN.Modules.BlogFriendlyUrlProvider.Entities;

namespace iFinity.DNN.Modules.BlogFriendlyUrlProvider
{
    /// <summary>
    /// This class is used to handle many Url-specific operations, principally around
    /// maintaining and keeping indexes of Friendly Urls and Querystrings.
    /// </summary>
    internal class UrlController
    {
        //keys used for cache entries for Urls and Querystrings
        private const string FriendlyUrlIndexKey = "ifty_bmp_BlogUrls_Tab{0}";
        private const string QueryStringIndexKey = "ifty_bmp_BlogQueryString_Tab{0}";
        private const string BlogCacheKey = "ifty_bmp_Blogs_{0}";
        private const string BlogEntriesCacheKey = "ifty_bmp_BlogEntries";
        /// <summary>
        /// Return the index of all the Friendly Urls for all the blog entries for the specified tab
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        internal static Hashtable GetBlogFriendlyUrlIndex(int tabId, int portalId, int startingEntryId, BlogModuleProvider provider)
        {
            string furlCacheKey = GetFriendlyUrlIndexKeyForTab(tabId);
            Hashtable friendlyUrlIndex = DataCache.GetCache<Hashtable>(furlCacheKey);
            if (friendlyUrlIndex == null)
            {
                string qsCacheKey = GetQueryStringIndexCacheKeyForTab(tabId);
                Hashtable queryStringIndex = null;
                //build index for tab
                BuildBlogUrlIndexes(tabId, portalId, startingEntryId, provider, out friendlyUrlIndex, out queryStringIndex);
                StoreIndexes(friendlyUrlIndex, furlCacheKey, queryStringIndex, qsCacheKey);
            }
            return friendlyUrlIndex;
        }
        /// <summary>
        /// Return the index of all the querystrings that belong to blog friendly urls for the specific tab.
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        internal static Hashtable GetBlogQueryStringIndex(int tabId, int portalId, int startingEntryId, BlogModuleProvider provider, bool forceRebuild)
        {
            string qsCacheKey = GetQueryStringIndexCacheKeyForTab(tabId);
            Hashtable queryStringIndex = DataCache.GetCache<Hashtable>(qsCacheKey);
            if (queryStringIndex == null || forceRebuild)
            {
                string furlCacheKey = GetFriendlyUrlIndexKeyForTab(tabId);
                Hashtable friendlyUrlIndex = null;
                //build index for tab
                BuildBlogUrlIndexes(tabId, portalId, startingEntryId, provider, out friendlyUrlIndex, out queryStringIndex);
                StoreIndexes(friendlyUrlIndex, furlCacheKey, queryStringIndex, qsCacheKey);
            }
            return queryStringIndex;
        }
        /// <summary>
        /// Rebuilds the two hashtables indexes for this provider.
        /// THese are then stored in the DNN cache
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="portalId"></param>
        /// <param name="provider"></param>
        internal static void RebuildBlogIndexes(int tabId, int portalId, int startingEntryId, BlogModuleProvider provider)
        {
            Hashtable queryStringIndex = null;
            Hashtable friendlyUrlIndex = null;
            string qsCacheKey = GetQueryStringIndexCacheKeyForTab(tabId);
            string furlCacheKey = GetFriendlyUrlIndexKeyForTab(tabId);
            //build index for tab
            BuildBlogUrlIndexes(tabId, portalId, startingEntryId, provider, out friendlyUrlIndex, out queryStringIndex);
            StoreIndexes(friendlyUrlIndex, furlCacheKey, queryStringIndex, qsCacheKey);
        }

        /// <summary>
        /// Return the caceh key for a tab blog index
        /// </summary>
        /// <param name="tabId"></param>
        /// <returns></returns>
        private static string GetFriendlyUrlIndexKeyForTab(int tabId)
        {
            return string.Format(FriendlyUrlIndexKey, tabId);
        }
        private static string GetQueryStringIndexCacheKeyForTab(int tabId)
        {
            return string.Format(QueryStringIndexKey, tabId);
        }
        /// <summary>
        /// Store the two indexes into the cache
        /// </summary>
        /// <param name="friendlyUrlIndex"></param>
        /// <param name="friendlyUrlCacheKey"></param>
        /// <param name="queryStringIndex"></param>
        /// <param name="queryStringCacheKey"></param>
        private static void StoreIndexes(Hashtable friendlyUrlIndex, string friendlyUrlCacheKey, Hashtable queryStringIndex, string queryStringCacheKey)
        {
            TimeSpan expire = new TimeSpan(24, 0, 0);
            DataCache.SetCache(friendlyUrlCacheKey, friendlyUrlIndex, expire);
            DataCache.SetCache(queryStringCacheKey, queryStringIndex, expire);
        }
        /// <summary>
        /// Build up the indexes for the Friendly Urls and the Querystrings
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="provider"></param>
        /// <param name="friendlyUrlIndex">out parameter.  The friendlyUrlIndex contains all the entry, tag, category and blog entries as a key, and returns the friendly Url for the specific item.</param>
        /// <param name="queryStringIndex">out parameter.  The queryStringIndex contains all the entry, tag, category and blog friendly Urls, and returns the specify querystring value for that entry</param>
        private static void BuildBlogUrlIndexes(int tabId, int portalId, int startingEntryId, BlogModuleProvider provider,out Hashtable friendlyUrlIndex,out Hashtable queryStringIndex)
        {
            friendlyUrlIndex = new Hashtable();
            queryStringIndex = new Hashtable();
            //call database procedure to get list of 
            BlogEntryInfoCol entries = null;
            BlogInfoCol blogs = null;
            TagInfoCol tags = null;
            CategoryInfoCol cats = null;
            Data.DataProvider.Instance().GetBlogEntriesForTab(tabId, startingEntryId, out entries, out blogs, out cats, out tags);
            if (entries != null && blogs != null)
            {
                //build up the dictionary
                foreach (BlogEntryInfo entry in entries)
                {
                    //friendly url index - look up by entryid, find Url
                    string furlKey = "e" + entry.EntryId.ToString();
                    string furlValue = MakeBlogEntryFriendlyUrl(entry.Title, entry.AddedDate, provider);
                    if (friendlyUrlIndex.ContainsKey(furlKey) == false)
                        friendlyUrlIndex.Add(furlKey, furlValue);

                    //querystring index - look up by url, find querystring for the blog entry
                    string qsKey = furlValue.ToLower();//the querystring lookup is the friendly Url value - but converted to lower case
                    string qsValue = "&EntryId=" + entry.EntryId.ToString();//the querystring is just the entryId parameter
                    if (provider.AlwaysUsesDnnPagePath(portalId) == false)//when not including the dnn page path into the friendly Url, then include the tabid in the querystring
                        qsValue = "?TabId=" + tabId.ToString() + qsValue;
                    if (queryStringIndex.ContainsKey(qsKey) == false)
                        queryStringIndex.Add(qsKey, qsValue);

                    //if the options aren't standard, also add in some other versions that will identify the right entry but will get redirected
                    if (provider.Options.PunctuationReplacement != "")
                    {
                        FriendlyUrlOptions altOptions = provider.Options.Clone();
                        altOptions.PunctuationReplacement = "";
                        string altQsKey = MakeBlogEntryFriendlyUrl(entry.Title, entry.AddedDate, provider, altOptions).ToLower();
                        if (altQsKey != qsKey)
                        {
                            if (queryStringIndex.ContainsKey(altQsKey) == false)
                                queryStringIndex.Add(altQsKey, qsValue);
                        }
                    }
                }
                foreach (BlogInfo blog in blogs)
                {
                    //friendly url index - look up by blogid, find Url
                    string furlKey = "b" + blog.BlogId.ToString();//key of blog entry
                    string furlValue = MakeBlogFriendlyUrl(blog, provider, provider.Options);//friendly Url for the base blog page
                    if (friendlyUrlIndex.ContainsKey(furlKey) == false)
                        friendlyUrlIndex.Add(furlKey, furlValue);
                    //querystring index, look up by url, find querystring for blog page
                    string qsKey = furlValue.ToLower();
                    string qsValue = "&BlogId=" + blog.BlogId.ToString();
                    if (queryStringIndex.ContainsKey(qsKey) == false)
                        queryStringIndex.Add(qsKey, qsValue);

                    //if the options aren't standard, also add in some other versions that will identify the right entry but will get redirected
                    if (provider.Options.PunctuationReplacement != "")
                    {
                        FriendlyUrlOptions altOptions = provider.Options.Clone();
                        altOptions.PunctuationReplacement = "";
                        string altQsKey = MakeBlogFriendlyUrl(blog, provider, provider.Options);//alternate friendly Url for the base blog page
                        if (altQsKey != qsKey)
                        {
                            if (queryStringIndex.ContainsKey(altQsKey) == false)
                                queryStringIndex.Add(altQsKey, qsValue);
                        }
                    }
                }
                foreach (CategoryInfo cat in cats)
                {
                    //friendly url index - look up by category id, find Url
                    string furlKey = "c" + cat.CatId.ToString();
                    string furlValue = MakeCategoryFriendlyUrl(cat, provider, provider.Options);
                    if (friendlyUrlIndex.ContainsKey(furlKey) == false)
                        friendlyUrlIndex.Add(furlKey, furlValue);

                    //querystring index - look up by url, find querystring for category
                    string qsKey = furlValue.ToLower();//always lowercase
                    string qsValue = "&catid=" + cat.CatId.ToString();
                    if (provider.AlwaysUsesDnnPagePath(portalId) == false)//when not including the dnn page path into the friendly Url, then include the tabid in the querystring
                        qsValue = "?TabId=" + tabId.ToString() + qsValue;

                    if (queryStringIndex.ContainsKey(qsKey) == false)
                        queryStringIndex.Add(qsKey, qsValue);

                }
                foreach (TagInfo tag in tags)
                {
                    //friendly url index - look up by tagid, find Url
                    string furlKey = "t" + tag.TagId.ToString();
                    string furlValue = MakeTagFriendlyUrl(tag, provider, provider.Options);
                    if (friendlyUrlIndex.ContainsKey(furlKey) == false)
                        friendlyUrlIndex.Add(furlKey, furlValue );

                    //querystring index - look up by url, find querystring for tag
                    string qsKey = furlValue.ToLower();//not case sensitive
                    string qsValue = "&tagid=" + tag.TagId.ToString();
                    if (provider.AlwaysUsesDnnPagePath(portalId) == false)//when not including the dnn page path into the friendly Url, then include the tabid in the querystring
                        qsValue = "?TabId=" + tabId.ToString() + qsValue;

                    if (queryStringIndex.ContainsKey(qsKey) == false)
                        queryStringIndex.Add(qsKey, qsValue);

                }

            }
        }

        /// <summary>
        /// Make up a friendly url path for a blog entry
        /// </summary>
        /// <param name="title"></param>
        /// <param name="date"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        internal static string MakeBlogEntryFriendlyUrl(string title, DateTime date, BlogModuleProvider provider, FriendlyUrlOptions options)
        {
            string urlName = provider.CleanNameForUrl(title, options);
            string urlFormat = "{0}/{1}/{2}/{3}";//wordpress format
            string friendlyUrlPath = string.Format(urlFormat, date.Year, date.Month.ToString("00"), date.Day.ToString("00"), urlName);
            string postPath = provider.PostPath;
            if (postPath != null && postPath != "")
            {
                //there is a path to put in front of the posts
                postPath = provider.CleanNameForUrl(postPath, options);
                friendlyUrlPath = postPath + "/" + friendlyUrlPath;
            }
            return friendlyUrlPath;
        }
        internal static string MakeBlogEntryFriendlyUrl(string title, DateTime date, BlogModuleProvider provider)
        {
            return MakeBlogEntryFriendlyUrl(title, date, provider, provider.Options);
        }
        /// <summary>
        /// Make up a friendly Url for a blog (ie, individual blog home page)
        /// </summary>
        /// <param name="blog"></param>
        /// <param name="provider"></param>
        /// <returns></returns>
        internal static string MakeBlogFriendlyUrl(BlogInfo blog, BlogModuleProvider provider, FriendlyUrlOptions options)
        {
            bool isFriendly;
            return MakeBlogFriendlyUrl(blog, provider, options, out isFriendly);
        }
        internal static string MakeBlogFriendlyUrl(BlogInfo blog, BlogModuleProvider provider, FriendlyUrlOptions options, out bool isFriendly)
        {
            string friendlyUrlPath = "";
            switch (provider.BlogTitle)
            {
                case BlogModuleProvider.BlogTitleOptions.Display_Name:
                    isFriendly = true;
                    friendlyUrlPath = provider.CleanNameForUrl(blog.DisplayName, options);
                    break;
                case BlogModuleProvider.BlogTitleOptions.Username:
                    isFriendly = true;
                    friendlyUrlPath = provider.CleanNameForUrl(blog.Username, options);
                    break;
                case BlogModuleProvider.BlogTitleOptions.None:
                    isFriendly = false;
                    friendlyUrlPath = "BlogId/" + blog.BlogId.ToString();
                    break;
                case BlogModuleProvider.BlogTitleOptions.Title:
                default:
                    friendlyUrlPath = provider.CleanNameForUrl(blog.Title, options);
                    isFriendly = true;
                    break;
            }
            if (friendlyUrlPath == "")
            {
                //fallback in case any of the values were missing
                friendlyUrlPath = "BlogId/" + blog.BlogId.ToString();
                isFriendly = false;
            }
            return friendlyUrlPath;
        }
        /// <summary>
        /// Make a friendly url for a category
        /// </summary>
        /// <param name="cat"></param>
        /// <param name="provider"></param>
        /// <param name="friendlyUrlOptions"></param>
        /// <returns></returns>
        internal static string MakeCategoryFriendlyUrl(CategoryInfo cat, BlogModuleProvider provider, FriendlyUrlOptions friendlyUrlOptions)
        {
            string friendlyUrlPath = "";
            string catPath = provider.CatPath;
            if (catPath != null && catPath != "")
                catPath = provider.CleanNameForUrl(catPath, friendlyUrlOptions) + "/";
            else
                catPath = "";

            string slug = cat.Slug;
            if (slug != null && slug != "")
            {
                if (slug.EndsWith(".aspx"))
                    slug = slug.Substring(0, slug.Length - 5);//take the .aspx from the end 
                //now make up the path
                friendlyUrlPath = catPath + slug;
            }
            else
                friendlyUrlPath = "catid/" + cat.CatId.ToString();
            //finished - return the result
            return friendlyUrlPath;
        }

        /// <summary>
        /// Make up a friendly url for a tag
        /// </summary>
        /// <param name="tag"></param>
        /// <param name="provider"></param>
        /// <param name="friendlyUrlOptions"></param>
        /// <returns></returns>
        internal static string MakeTagFriendlyUrl(TagInfo tag, BlogModuleProvider provider, FriendlyUrlOptions friendlyUrlOptions)
        {
            string friendlyUrlPath = "";
            //pretty simple really - check for tag leading option, if none, then just output the slug
            string tagPath = provider.TagPath;
            if (tagPath != null && tagPath != "")
                //just clean it up in case
                tagPath = provider.CleanNameForUrl(tagPath, friendlyUrlOptions) + "/";
            else
                tagPath = "";

            string slug = tag.Slug;
            if (slug != null && slug != "")
            {
                if (slug.EndsWith(".aspx"))
                    slug = slug.Substring(0, slug.Length - 5);//remove the .aspx from the end.  The module will put back whatever is the current extension
                //create the friendly url path
                friendlyUrlPath = tagPath + slug;
            }
            else
            {
                friendlyUrlPath = "tagid/" + tag.TagId.ToString();
            }
            //finished
            return friendlyUrlPath;
        }
        /// <summary>
        /// Creates a Url from the provided values
        /// </summary>
        /// <param name="tabId"></param>
        /// <param name="portalId"></param>
        /// <param name="httpAlias"></param>
        /// <param name="controlKey"></param>
        /// <param name="additionalParameters"></param>
        /// <returns></returns>
        internal static string FriendlyUrl(int tabId, int portalId, string httpAlias, string controlKey, params string[] additionalParameters)
        {
            string friendlyUrl = null;
            DotNetNuke.Entities.Portals.PortalAliasController pac = new DotNetNuke.Entities.Portals.PortalAliasController();
            DotNetNuke.Entities.Portals.PortalAliasInfo portalAlias = pac.GetPortalAlias(httpAlias, portalId);
            friendlyUrl = FriendlyUrl(tabId, portalId, portalAlias, controlKey, additionalParameters);
            return friendlyUrl;

        }
        internal static string FriendlyUrl(int tabId, int portalId, DotNetNuke.Entities.Portals.PortalAliasInfo portalAlias, string controlKey, params string[] additionalParameters)
        {
            string friendlyUrl = null;
            if (portalAlias != null)
            {
                DotNetNuke.Entities.Portals.PortalSettings portalSettings = new DotNetNuke.Entities.Portals.PortalSettings(tabId, portalAlias);
                friendlyUrl = DotNetNuke.Common.Globals.NavigateURL(tabId, portalSettings, controlKey, additionalParameters);
            }
            return friendlyUrl;
        }

        internal static BlogInfo GetBlog(int portalId, int blogId)
        {
            Dictionary<int, BlogInfo> blogs = GetBlogs(portalId, false);
            if (blogs.ContainsKey(blogId))
            {
                return blogs[blogId];
            }
            else
            {
                blogs = GetBlogs(portalId, true);
                if (blogs.ContainsKey(blogId))
                {
                    return blogs[blogId];
                }
                else
                    return null;
            }
        }
        /// <summary>
        /// Returns a blog entry given the entryId
        /// </summary>
        /// <param name="entryId"></param>
        /// <remarks>This method uses a cached hashtable of entries that is filled as entries are requested.
        /// In this respect, a database lookup is used for each entry, but if multiple requests are made
        /// within the cache lifetime, the cache is used to return subsequent requests.</remarks>
        /// <returns></returns>
        internal static BlogEntryInfo GetEntry(int entryId)
        {
            BlogEntryInfo entry = null;
            bool entryFound = false;
            //get table of entries from cache
            Hashtable blogEntries = DataCache.GetCache<Hashtable>(BlogEntriesCacheKey);
            if (blogEntries == null)
            {
                //no existing item cached
                blogEntries = new Hashtable();
            }
            else
            {
                //cached table, look for entry in it
                if (blogEntries.ContainsKey(entryId))
                {
                    entry = (BlogEntryInfo)blogEntries[entryId];
                    entryFound = true;
                }
            }
            if (entryFound == false)
            {
                //get from database
                entry = Data.DataProvider.Instance().GetBlogEntry(entryId);
                //add to cache table
                blogEntries.Add(entryId, entry);
                //cache
                DataCache.SetCache(BlogEntriesCacheKey, blogEntries);
            }
            return entry;
            
        }
        private static Dictionary<int, BlogInfo> GetBlogs(int portalId, bool bypassCache)
        {
            string blogKey = string.Format(BlogCacheKey, portalId);
            Dictionary<int, BlogInfo> result = null;
            if (bypassCache == false)
                result = DataCache.GetCache<Dictionary<int, BlogInfo>>(blogKey);
            if (result == null || bypassCache)
            {
                result = Data.DataProvider.Instance().GetBlogsForPortal(portalId);
                if (result != null)
                {
                    DataCache.SetCache(blogKey, result);
                }
            }
            return result;
        }
    }
}
